iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 21
0
Microsoft Azure

Azure 的自我修煉系列 第 21

Day 21 實作 Razor ASP.NET Core 中的頁面單元測試

  • 分享至 

  • xImage
  •  

實作Razor ASP.NET Core 中的頁面單元測試

延續 Day09 實作官網 ASP.NET Core 教學我們把專案加入 xUnit Test

測試架構為 xUnit。 物件模擬架構為 Moq。

創建測試專案

在專案目錄下加入 tests 資料夾,增加 xUnit 專案

dotnet new xunit -o Tests

安裝套件

dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.AspNetCore.TestHost
dotnet add package Moq
dotnet add package Newtonsoft.Json
dotnet add package System.Diagnostics.TraceSource
dotnet add package System.Net.Http

增加參考

dotnet add ./Tests.csproj reference ../PellokITHome.csproj  

Utilities class

創建 Utilities

mkdir Utilities
touch Utilities/Utilities.cs

Utilities.cs的內容如下:

using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using PellokITHome.Data;

namespace Tests
{
    public static class Utilities
    {
        #region snippet1
        public static DbContextOptions<PellokITHomeContext> TestDbContextOptions()
        {
            // Create a new service provider to create a new in-memory database.
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();

            // Create a new options instance using an in-memory database and 
            // IServiceProvider that the context should resolve all of its 
            // services from.
            var builder = new DbContextOptionsBuilder<PellokITHomeContext>()
                .UseInMemoryDatabase("InMemoryDb")
                .UseInternalServiceProvider(serviceProvider);

            return builder.Options;
        }
        #endregion
    }
}

DataAccessLayerTest.cs

創建資料測試

mkdir UnitTests
touch UnitTests/DataAccessLayerTest.cs

內容如下:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Xunit;
using PellokITHome.Data;
using PellokITHome.Models;

namespace Tests.UnitTests
{
    public class DataAccessLayerTest
    {
        [Fact]
        public async Task GetMessagesAsync_MessagesAreReturned()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var expectedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(expectedMessages);
                await db.SaveChangesAsync();

                // Act
                var result = await db.GetArticlesAsync();

                // Assert
                var actualMessages = Assert.IsAssignableFrom<List<Article>>(result);
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
            }
        }

        [Fact]
        public async Task AddMessageAsync_MessageIsAdded()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var recId = 10;
                var expectedMessage = new Article() { ID = recId, Title = "Message" };

                // Act
                await db.AddArticleAsync(expectedMessage);

                // Assert
                var actualMessage = await db.FindAsync<Article>(recId);
                Assert.Equal(expectedMessage, actualMessage);
            }
        }

        [Fact]
        public async Task DeleteAllMessagesAsync_MessagesAreDeleted()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var seedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(seedMessages);
                await db.SaveChangesAsync();

                // Act
                await db.DeleteAllArticlesAsync();

                // Assert
                Assert.Empty(await db.Articles.AsNoTracking().ToListAsync());
            }
        }

        [Fact]
        public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                #region snippet1
                // Arrange
                var seedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(seedMessages);
                await db.SaveChangesAsync();
                var recId = 1;
                var expectedMessages = 
                    seedMessages.Where(message => message.ID != recId).ToList();
                #endregion

                #region snippet2
                // Act
                await db.DeleteArticleAsync(recId);
                #endregion

                #region snippet3
                // Assert
                var actualMessages = await db.Articles.AsNoTracking().ToListAsync();
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
                #endregion
            }
        }

        #region snippet4
        [Fact]
        public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var expectedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(expectedMessages);
                await db.SaveChangesAsync();
                var recId = 4;

                // Act
                try
                {
                    await db.DeleteArticleAsync(recId);
                }
                catch
                {
                    // recId doesn't exist
                }

                // Assert
                var actualMessages = await db.Articles.AsNoTracking().ToListAsync();
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
            }
        }
        #endregion
    }
}

修改PellokITHome專案

修改PellokITHomeContext.cs檔案

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PellokITHome.Models;

namespace PellokITHome.Data
{
    public class PellokITHomeContext : DbContext
    {
        public PellokITHomeContext (
            DbContextOptions<PellokITHomeContext> options)
            : base(options)
        {
        }

        public DbSet<PellokITHome.Models.Article> Articles { get; set; }

        #region snippet1
        public async virtual Task<List<Article>> GetArticlesAsync()
        {
            return await Articles
                .OrderBy(Article => Article.Title)
                .AsNoTracking()
                .ToListAsync();
        }
        #endregion

        #region snippet2
        public async virtual Task AddArticleAsync(Article Article)
        {
            await Articles.AddAsync(Article);
            await SaveChangesAsync();
        }
        #endregion

        #region snippet3
        public async virtual Task DeleteAllArticlesAsync()
        {
            foreach (Article Article in Articles)
            {
                Articles.Remove(Article);
            }
            
            await SaveChangesAsync();
        }
        #endregion

        #region snippet4
        public async virtual Task DeleteArticleAsync(int id)
        {
            var Article = await Articles.FindAsync(id);

            if (Article != null)
            {
                Articles.Remove(Article);
                await SaveChangesAsync();
            }
        }
        #endregion

        public void Initialize()
        {
            Articles.AddRange(GetSeedingArticles());
            SaveChanges();
        }

        public static List<Article> GetSeedingArticles()
        {
            return new List<Article>()
            {
                new Article(){ Title = "Day01 Azure 的自我修煉" },
                new Article(){ Title = "Day02 申請Azure帳號" },
                new Article(){ Title = "Day03 Resource Group 資源群組" }
            };
        }
    }
}

修改以上內容後,需要重建DB,確認專案沒有問題,執行正常。

執行測試

dotnet test

https://ithelp.ithome.com.tw/upload/images/20200921/20072651DdJy1B0RiS.png

解決方案

合併兩個專案為一個解決方案

# 創建方案
dotnet new sln -o PellokITHomePipeline

# 到方案目錄下
cd PellokITHomePipeline

# 複製專案
cp -r ../PellokITHome .
# 移除專案暫存檔
rm -rf PellokITHome/.git
rm -rf PellokITHome/.vscode
rm -rf PellokITHome/obj
rm -rf PellokITHome/bin

# 複製專案
cp -r ../Tests .
# 移除專案暫存檔
rm -rf Tests/.git
rm -rf Tests/.vscode
rm -rf Tests/obj
rm -rf Tests/bin

# 加入專案到方案
dotnet sln add ./PellokITHome/PellokITHome.csproj
dotnet sln add ./Tests/Tests.csproj

https://ithelp.ithome.com.tw/upload/images/20200921/20072651wOiv2KRyZh.png

增加Git版控

新增 .gitignore

*/bin/*
*/obj/*
*/.vscode/*
*.db

提交索引、提交版本

mv PellokITHome/.gitignore .
git init
git status
git commit -m "first commit"

https://ithelp.ithome.com.tw/upload/images/20200921/20072651GMjMXpF9Kg.png

測試執行

code -r .

# 測試
dotnet test

相關連結:

上一篇 Day20 實作 Dotnet Test 測試範例
下一篇 Day22 整合CI測試到 Azure Pipeline 服務


上一篇
Day20 實作 Dotnet Test 測試範例
下一篇
Day22 整合CI測試到 Azure Pipeline 服務
系列文
Azure 的自我修煉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言